//
//  BMSnomPhone.m
//  snomPhone
//
//  Created by Daniel Hazelbaker on 11/2/08.
//  Copyright 2008 Blue Box Moon. All rights reserved.
//

#import "BMSnomPhone.h"
#import "preferences.h"


static BMSnomPhone *_sharedPhone = nil;
static NSDictionary *_countryCodes = nil;

@implementation BMSnomPhone


+ (BMPreferences *)preferences;
{
    BMPreferences *preferences = [BMPreferences preferencesWithIdentifier:@"com.blueboxmoon.MacSnome"];
    
    if ([[preferences valueForKey:@"ConvertedSnomPhone"] boolValue] != YES) {
        //
        // Copy over all preferences from the old identifier and mark that we have converted.
        //
        [preferences takeValuesFromIdentifier:@"com.blueboxmoon.snomPhone"];
        [preferences setValue:[NSNumber numberWithBool:YES] forKey:@"ConvertedSnomPhone"];
        [preferences synchronize];
    }
    
    return preferences;
}


//
// Retrieve the current snom Phone object.
//
+ (BMSnomPhone *)sharedPhone
{
    return _sharedPhone;
}


+ (NSDictionary *)countryCodes
{
    if (_countryCodes == nil) {
        NSMutableDictionary *codes = [[NSMutableDictionary alloc] init];
        NSArray *data;
        
        data = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"CountryCodes" ofType:@"plist"]];
        if (data == nil) {
            data = [NSArray arrayWithContentsOfFile:[NSString stringWithFormat:@"%@/Contents/Resources/CountryCodes.plist", [[NSBundle mainBundle] pathForResource:@"MacSnomeStatus" ofType:@"app"]]];
            if (data == nil)
                [NSException raise:NSGenericException format:@"Could not find CountryCodes resource."];
        }
        
        for (NSDictionary *dict in data)
        {
            if ([codes valueForKey:[dict valueForKey:@"CountryCode"]] == nil)
                [codes setValue:dict forKey:[dict valueForKey:@"CountryCode"]];
        }
        
        _countryCodes = codes;
    }

    return _countryCodes;
}


//
// Find the local country code information.
//
+ (NSDictionary *)localCountryCode
{
    BMPreferences *preferences = [self preferences];
    NSString *iso = [preferences valueForKey:kCountryCode];
    

    if (iso == nil)
        return [self countryCodeByISO:[[NSLocale autoupdatingCurrentLocale] objectForKey:NSLocaleCountryCode]];
    else
        return [self countryCodeByISO:iso];
}


//
// Find the dictionary containing the information given the country code (i.e. "1")
//
+ (NSDictionary *)countryCode:(NSString *)code
{
    return [[self countryCodes] valueForKey:code];
}


//
// Find the dictionary containing the information given the ISO country code (i.e. "US")
//
+ (NSDictionary *)countryCodeByISO:(NSString *)code
{
    NSDictionary *codes = [self countryCodes];
    
    
    for (NSDictionary *dict in [codes allValues]) {
        if ([[dict valueForKey:@"Code"] isEqual:code])
            return dict;
    }
    
    return nil;
}


//
// Find the dictionary containing the information given the ISO country code (i.e. "17605555555")
//
+ (NSDictionary *)countryCodeByPhone:(NSString *)number
{
    NSDictionary *codes = [self countryCodes];
    NSString *num;
    int len;
    

    for (len = 1; len <= [number length]; len++) {
        num = [number substringToIndex:len];

        for (NSDictionary *dict in [codes allValues]) {
            if ([[dict valueForKey:@"CountryCode"] isEqual:num])
                return dict;
        }
    }
    
    return nil;
}


//
// Strip the phone number so only digits are left.
//
+ (NSString *)strippedPhoneNumber:(NSString *)phone
{
    NSMutableString *phoneString = [NSMutableString string];
    NSCharacterSet  *numericCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
    int		    i;


    for (i = 0; i < [phone length]; i++) {
	if ([numericCharacterSet characterIsMember:[phone characterAtIndex:i]])
	    [phoneString appendFormat:@"%c", [phone characterAtIndex:i]];
    }

    return phoneString;
}


//
// Format a phone number to be a standard number.
//
+ (NSString *)formatPhoneNumber:(NSString *)number
{
    NSMutableString *final = [NSMutableString string];
    NSDictionary *code = nil, *local = [self localCountryCode];
    NSString *format;
    int number_idx, format_idx;


    //
    // Strip the phone number and find the country code info.
    //
    number = [self strippedPhoneNumber:number];
    if ([number length] < 7)
        return number;

    //
    // Try to strip out a possible regional/national dialing code.
    //
    if (local != nil) {
        NSString *national, *international;
        
        national = [local valueForKey:@"NationalPrefix"];
        international = [local valueForKey:@"InternationalPrefix"];
        if ([national length] > [international length]) {
            if ([national length] > 0 && [national isEqual:[number substringWithRange:NSMakeRange(0, [national length])]]) {
                number = [number substringFromIndex:[national length]];
                code = local;
            }
            else if ([international length] > 0 && [international isEqual:[number substringWithRange:NSMakeRange(0, [international length])]])
                number = [number substringFromIndex:[international length]];
        }
        else {
            if ([international length] > 0 && [international isEqual:[number substringWithRange:NSMakeRange(0, [international length])]])
                number = [number substringFromIndex:[international length]];
            else if ([national length] > 0 && [national isEqual:[number substringWithRange:NSMakeRange(0, [national length])]]) {
                number = [number substringFromIndex:[national length]];
                code = local;
            }
        }
    }

    //
    // Find the target country code.
    //
    if (code == nil) {
        code = [self countryCodeByPhone:number];
        if (code == nil)
            return number;
        number = [number substringFromIndex:[[code valueForKey:@"CountryCode"] length]];
    }

    //
    // Get the format to use.
    //
    format = [code valueForKey:@"NumberFormat"];

    for (format_idx = 0, number_idx = 0; format_idx < [format length]; format_idx++)
    {
        if ([format characterAtIndex:format_idx] == '#') {
            [final appendString:[number substringWithRange:NSMakeRange(number_idx, 1)]];
            if (++number_idx >= [number length])
                return final;
        }
        else
            [final appendString:[format substringWithRange:NSMakeRange(format_idx, 1)]];
    }
    
    [final appendString:[number substringFromIndex:number_idx]];
    
    return final;
}


//
// Format a phone number for dialing on the phone.  This means take the +, if there
// is one, and replace it with the correct prefix information.
//
+ (NSString *)formatPhoneNumberForDialing:(NSString *)number
{
    NSDictionary *code, *localCode;
    
    
    //
    // Strip the phone number and find the country code info.
    //
    if ([number characterAtIndex:0] != '+') {
        return [self strippedPhoneNumber:number];
    }
    number = [self strippedPhoneNumber:number];
    code = [self countryCodeByPhone:number];
    if ([number length] < 7)
        return number;

    //
    // Get the local country code information.
    //
    localCode = [self localCountryCode];
    if (localCode == nil)
        return number;

    if (localCode == code) {
        //
        // Strip the country code information and get the format to use.
        //
        number = [number substringFromIndex:[[localCode valueForKey:@"CountryCode"] length]];
        number = [NSString stringWithFormat:@"%@%@", [localCode valueForKey:@"NationalPrefix"], number];
    }
    else {
        number = [NSString stringWithFormat:@"%@%@", [localCode valueForKey:@"InternationalPrefix"], number];
    }
    
    return number;
}


//
// Initialize a new snome phone object with the given address.
// If the address is nil then no object is created and nil is
// returned. Otherwise it can be either a numeric IP address or
// a DNS name to lookup.
//
- (id)initWithAddress:(NSString *)address
{
    return [self initWithAddress:address username:nil password:nil];
}


//
// Initialize a new snom phone object with the given address.
// If the address is nil then no object is created and nil is
// returned. Otherwise it can be either a numeric IP address or
// a DNS name to lookup. If password is not empty then the password
// is used to communicate with the snom phone.
//
- (id)initWithAddress:(NSString *)address username:(NSString *)username password:(NSString *)password
{
    if ([self init] == nil)
	return nil;

    //
    // Check if we have a (somewhat) valid address.
    //
    if ([address length] == 0) {
	[self release];
	[NSException raise:NSInvalidArgumentException format:@"Address cannot be an empty string."];

	return nil;
    }

    //
    // Store the information for later retrieval.
    //
    [self setValue:address forKey:@"phoneAddress"];
    [self setValue:username forKey:@"phoneUsername"];
    [self setValue:password forKey:@"phonePassword"];

    //
    // Do an initial check to see if we can talk to the phone.
    //
    if ([[self requestPhoneURL:@"/index.htm" postData:nil] length] == 0) {
	[self release];
	[NSException raise:NSGenericException format:@"Cannot contact phone web service."];

	return nil;
    }

    //
    // Retrieve the initial settings from the phone.
    //
    [self loadCallHistory];
    [self loadPhoneSettings];

    _sharedPhone = self;
    
    return self;
}


//
// Release all memory and objects used by this snom phone object.
//
- (void)dealloc
{
    [self setValue:nil forKey:@"phoneUsername"];
    [self setValue:nil forKey:@"phonePassword"];
    [self setValue:nil forKey:@"phoneAddress"];
    [self setValue:nil forKey:@"callHistory"];
    [self setValue:nil forKey:@"phoneSettings"];

    [super dealloc];
}


//
// Build the phone URL required for the given urlPath. This path should be
// absolute (i.e. "/index.html").
//
- (NSURL *)buildPhoneURL:(NSString *)urlPath
{
    //
    // Build up the URL and the request.
    //
    if ([phoneUsername length] > 0 && [phonePassword length] > 0)
	return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%@@%@%@", phoneUsername, phonePassword, phoneAddress, urlPath]];
    else
	return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@%@", phoneAddress, urlPath]];
}


//
// Request a URL from the phone. This path should be absolute (i.e. "/index.html").
// The result data is returned as a string object. If postData is not empty then
// the request is sent as an HTTP POST method with the given postData. If an error
// occured then nil is returned.
//
- (NSString *)requestPhoneURL:(NSString *)urlPath postData:(NSString *)postData
{
    NSMutableURLRequest	*request;
    NSHTTPURLResponse	*response;
    NSString		*string;
    NSError		*error = nil;
    NSData		*data;
    NSURL		*url;


    //
    // Build up the URL and the request.
    //
    url = [self buildPhoneURL:urlPath];
    request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];

    //
    // Setup the postData if applicable.
    //
    if ([postData length] > 0) {
	[request setHTTPMethod:@"POST"];
	[request setHTTPBody:[postData dataUsingEncoding:NSUTF8StringEncoding]];
    }

    //
    // Submit the URL request.
    //
    data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    if ([response statusCode] == 401)
        [NSException raise:NSGenericException format:@"401 Unauthorized"];

    //
    // Check for valid data and conver to a string.
    //
    if (data == nil)
	return nil;
    string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

    //
    // Check if the string is saying we are unauthorized.
    //
    if ([string length] < 2048 && ([string rangeOfString:@"Unauthorized request"].location != NSNotFound ||
	[string rangeOfString:@"<B>Login failed!</B>"].location != NSNotFound))
	[NSException raise:NSGenericException format:@"401 Unauthorized"];

    return string;
}


//
// Load the call history from the phone. We don't do any major error checking
// but if the XML doesn't line up then we just give up.
//
- (void)loadCallHistory
{
    NSMutableDictionary *history = [NSMutableDictionary dictionary];
    NSMutableArray      *calls;
    NSXMLDocument       *document;
    NSDictionary        *call;
    NSXMLNode           *parent;
    NSArray             *nodes;
    NSError             *error = nil;
    int                 i;
    
    
    //
    // Retrieve the web page from the Snom phone.
    //
    document = [[[NSXMLDocument alloc] initWithXMLString:[self requestPhoneURL:@"/index.htm" postData:nil] options:(NSXMLDocumentTidyHTML|NSXMLDocumentTidyXML) error:&error] autorelease];
    if (document == nil)
        return;
    
    //
    // Process dialed calls.
    //
    nodes = [document objectsForXQuery:@".//a[@name=\"list_dialed\"]" error:nil];
    calls = [NSMutableArray array];
    if ([nodes count] > 0) {
        parent = [[[[nodes objectAtIndex:0] parent] parent] parent];
        for (i = 3; i < [parent childCount] && [calls count] < 10; i++) {
            call = [self processCallHistoryNode:[parent childAtIndex:i] isMissed:NO];
            if (call != nil)
                [calls addObject:call];
        }
    }
    [history setValue:calls forKey:@"dialedCalls"];
    
    //
    // Process missed calls.
    //
    nodes = [document objectsForXQuery:@".//a[@name=\"list_missed\"]" error:nil];
    calls = [NSMutableArray array];
    if ([nodes count] > 0) {
        parent = [[[[nodes objectAtIndex:0] parent] parent] parent];
        for (i = 3; i < [parent childCount] && [calls count] < 10; i++) {
            call = [self processCallHistoryNode:[parent childAtIndex:i] isMissed:YES];
            if (call != nil)
                [calls addObject:call];
        }
    }
    [history setValue:calls forKey:@"missedCalls"];
    
    //
    // Process received calls.
    //
    nodes = [document objectsForXQuery:@".//a[@name=\"list_received\"]" error:nil];
    calls = [NSMutableArray array];
    if ([nodes count] > 0) {
        parent = [[[[nodes objectAtIndex:0] parent] parent] parent];
        for (i = 3; i < [parent childCount] && [calls count] < 10; i++) {
            call = [self processCallHistoryNode:[parent childAtIndex:i] isMissed:NO];
            if (call != nil)
                [calls addObject:call];
        }
    }
    [history setValue:calls forKey:@"receivedCalls"];
    
    [self setValue:history forKey:@"callHistory"];
}


//
// Process a call history node from the XML data. If the missed flag is
// turned on then the node is a missed call record, otherwise it is a
// dialed or received call record.
//
- (NSDictionary *)processCallHistoryNode:(NSXMLNode *)node isMissed:(BOOL)missed
{
    NSCalendarDate      *callDate, *now;
    NSCharacterSet      *whitespace = [NSCharacterSet whitespaceCharacterSet];
    NSString            *date = nil, *time = nil, *duration = nil, *number = nil, *name = nil,
    *relativeDuration = nil, *relativeShortDuration = nil, *relativeDate = nil,
    *relativeDateTime = nil, *dateFormat, *timeFormat;
    NSArray             *components, *comp;
    int                 hours, minutes, seconds;
    
    
    //
    // Get the values from the node and trim off anything we don't want.
    //
    if (missed == NO) {
        date = [[[node childAtIndex:0] stringValue] stringByTrimmingCharactersInSet:whitespace];
        time = [[[node childAtIndex:1] stringValue] stringByTrimmingCharactersInSet:whitespace];
        duration = [[[node childAtIndex:2] stringValue] stringByTrimmingCharactersInSet:whitespace];
        number = [[[node childAtIndex:5] stringValue] stringByTrimmingCharactersInSet:whitespace];
        if ([number length] == 0)
            number = [[[node childAtIndex:6] stringValue] stringByTrimmingCharactersInSet:whitespace];
    }
    else {
        date = [[[node childAtIndex:0] stringValue] stringByTrimmingCharactersInSet:whitespace];
        time = [[[node childAtIndex:1] stringValue] stringByTrimmingCharactersInSet:whitespace];
        number = [[[node childAtIndex:4] stringValue] stringByTrimmingCharactersInSet:whitespace];
        if ([number length] == 0)
            number = [[[node childAtIndex:5] stringValue] stringByTrimmingCharactersInSet:whitespace];
    }
    
    //
    // Split out the number and name.
    //
    components = [number componentsSeparatedByString:@" <"];
    if ([components count] == 1) {
        components = [number componentsSeparatedByString:@":"];
        comp = [number componentsSeparatedByString:@"\n"];
        if ([components count] == 1)
            return nil;
        else {
            number = [[[components objectAtIndex:1] componentsSeparatedByString:@"@"] objectAtIndex:0];
            if ([comp count] == 1)
                name = number;
            else
                name = [[comp objectAtIndex:1] stringByTrimmingCharactersInSet:whitespace];
        }
    }
    else {
        name = [components objectAtIndex:0];
        number = [[[[[components objectAtIndex:1] componentsSeparatedByString:@":"] objectAtIndex:1] componentsSeparatedByString:@"@"] objectAtIndex:0];
    }
    
    //
    // Parse the date.
    //
    if ([time characterAtIndex:([time length] - 1)] == 'M')
        timeFormat = @"%I:%M%p";
    else
        timeFormat = @"%H:%M";
    if ([date rangeOfString:@"/"].location != NSNotFound)
        dateFormat = @"%m/%e/%Y";
    else
        dateFormat = @"%e.%m.%Y";
    callDate = [NSCalendarDate dateWithString:[NSString stringWithFormat:@"%@ %@", date, time] calendarFormat:[NSString stringWithFormat:@"%@ %@", dateFormat, timeFormat]];
    now = [NSCalendarDate date];
    if ([now dayOfCommonEra] == [callDate dayOfCommonEra]) {
        relativeDate = @"Today";
        relativeDateTime = time;
    }
    else if (([now dayOfCommonEra] - 1) == [callDate dayOfCommonEra]) {
        relativeDate = @"Yesterday";
        relativeDateTime = [NSString stringWithFormat:@"Yesterday at %@", time];
    }
    else {
        relativeDate = date;
        relativeDateTime = [NSString stringWithFormat:@"%@ on %@", time, date];
    }
    
    //
    // Parse the time, convert the "AM/PM" designation to just "a/p".
    //
    if ([time characterAtIndex:([time length] - 1)] == 'M')
    {
        if ([[time substringWithRange:NSMakeRange([time length] - 2, 2)] isEqual:@"AM"])
            time = [NSString stringWithFormat:@"%@a", [time substringToIndex:([time length] - 2)]];
        else
            time = [NSString stringWithFormat:@"%@p", [time substringToIndex:([time length] - 2)]];
    }
    
    //
    // Parse the duration.
    //
    components = [duration componentsSeparatedByString:@":"];
    if ([components count] == 3) {
        hours = [[components objectAtIndex:0] intValue];
        minutes = [[components objectAtIndex:1] intValue];
        seconds = [[components objectAtIndex:2] intValue];
    }
    else if ([duration characterAtIndex:([duration length] - 1)] == 'h') {
        hours = [[components objectAtIndex:0] intValue];
        minutes = [[components objectAtIndex:1] intValue];
        seconds = 0;
    }
    else {
        hours = 0;
        minutes = [[components objectAtIndex:0] intValue];
        seconds = [[components objectAtIndex:1] intValue];
    }
    if (hours > 0) {
        duration = [NSString stringWithFormat:@"%d:%02d:%02d", hours, minutes, seconds];
        relativeDuration = [NSString stringWithFormat:@"%d hour%s, %d minute%s and %d second%s",
                            hours, (hours > 1 ? "s" : ""), minutes, (minutes > 1 ? "s" : ""), seconds, (seconds > 1 ? "s" : "")];
        relativeShortDuration = [NSString stringWithFormat:@"%d hour%s, %d min%s and %d sec%s",
                                 hours, (hours > 1 ? "s" : ""), minutes, (minutes > 1 ? "s" : ""), seconds, (seconds > 1 ? "s" : "")];
    }
    else if (minutes > 0) {
        duration = [NSString stringWithFormat:@"%d:%02d", minutes, seconds];
        relativeDuration = [NSString stringWithFormat:@"%d minute%s and %d second%s",
                            minutes, (minutes > 1 ? "s" : ""), seconds, (seconds > 1 ? "s" : "")];
        relativeShortDuration = [NSString stringWithFormat:@"%d min%s and %d sec%s",
                                 minutes, (minutes > 1 ? "s" : ""), seconds, (seconds > 1 ? "s" : "")];
    }
    else {
        duration = [NSString stringWithFormat:@"0:%02d", seconds];
        relativeDuration = [NSString stringWithFormat:@"%d second%s",
                            seconds, (seconds > 1 ? "s" : "")];
        relativeShortDuration = [NSString stringWithFormat:@"%d sec%s",
                                 seconds, (seconds > 1 ? "s" : "")];
    }
    
    //
    // Build the call dictionary.
    //
    if (missed == NO) {
        return [NSDictionary dictionaryWithObjectsAndKeys:
                name, @"name",
                number, @"number",
                date, @"date",
                time, @"time",
                relativeDate, @"relativeDate",
                relativeDateTime, @"relativeDateTime",
                callDate, @"callDate",
                [NSString stringWithFormat:@"%@ %@", date, time], @"dateTime",
                duration, @"duration",
                relativeDuration, @"relativeDuration",
                relativeShortDuration, @"relativeShortDuration",
                nil];
    }
    else {
        return [NSDictionary dictionaryWithObjectsAndKeys:
                name, @"name",
                number, @"number",
                date, @"date",
                time, @"time",
                relativeDate, @"relativeDate",
                relativeDateTime, @"relativeDateTime",
                callDate, @"callDate",
                [NSString stringWithFormat:@"%@ %@", date, time], @"dateTime",
                nil];
    }
}


//
// Load the phone settings from the phone. We don't really do anything except
// store all the settings in a dictionary that can be retrieved at a later
// time.
//
- (void)loadPhoneSettings
{
    NSMutableDictionary	*settings;
    NSCharacterSet	*codeCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"&!"];
    NSString		*settingsString, *key, *value;
    NSArray		*array, *components;
    int			i;


    if (phoneSettingsDate == nil || [phoneSettingsDate timeIntervalSinceNow] < -3)
    {
        //
        // Retrieve the web page from the Snom phone.
        //
        settings = [NSMutableDictionary dictionary];
        settingsString = [self requestPhoneURL:@"/settings.cfg" postData:nil];
        array = [settingsString componentsSeparatedByString:@"\r\n"];
        for (i = 0; i < [array count]; i++) {
            //
            // Split this setting out to its key/value pair.
            //
            components = [[array objectAtIndex:i] componentsSeparatedByString:@": "];
            if ([components count] != 2)
                continue;
            key = [components objectAtIndex:0];
            value = [components objectAtIndex:1];
            
            //
            // Strip off any special characters from the key.
            //
            key = [key stringByTrimmingCharactersInSet:codeCharacterSet];
            
            [settings setObject:value forKey:key];
        }
        
        [self setValue:settings forKey:@"phoneSettings"];
        [self setValue:[NSDate date] forKey:@"phoneSettingsDate"];
    }
}


//
// Set a phone setting. If you are setting more than one key/value pair then
// you should use the setPhoneSettingsFromDictionary: method to process all
// settings at once.
//
- (void)setPhoneSetting:(id)value forKey:(NSString *)key
{
    if ([self requestPhoneURL:@"/prefs.htm" postData:[NSString stringWithFormat:@"%@=%@&Settings=Save", key, value]] != nil)
	[phoneSettings setObject:value forKey:key];
}


//
// Set a group of phone settings at once.
//
- (void)setPhoneSettingsFromDictionary:(NSDictionary *)settings
{
    NSMutableString	*post;
    NSArray		*keys;
    int			i;


    //
    // Walk all the settings and put them in the post data.
    //
    keys = [settings allKeys];
    post = [NSMutableString string];
    for (i = 0; i < [keys count]; i++) {
	if (i > 0)
	    [post appendString:@"&"];
	[post appendFormat:@"%@=%@", [keys objectAtIndex:i], [settings objectForKey:[keys objectAtIndex:i]]];
    }

    //
    // Append the final "save" command.
    //
    [post appendFormat:@"&Settings=Save"];

    if ([self requestPhoneURL:@"/prefs.htm" postData:post] != nil)
	[phoneSettings setValuesForKeysWithDictionary:settings];
}


//
// Retrieve the cached call history from the phone.
//
- (NSDictionary *)callHistory
{
    return callHistory;
}


//
// Retrieve the cached phone settings from the phone.
//
- (NSDictionary *)phoneSettings
{
    return phoneSettings;
}


//
// Dial the given phone number on the phone.
//
- (void)dialPhoneNumber:(NSString *)phoneNumber
{
    NSString	*urlString;


    urlString = [NSString stringWithFormat:@"/index.htm?number=%@", [[self class] formatPhoneNumberForDialing:phoneNumber]];
    [self requestPhoneURL:urlString postData:nil];
}


@end
